/*-----------------------------------------------------------------------------
	The contents of this file are subject to the Mozilla Public License
	Version 1.1 (the "License"); you may not use this file except in
	compliance with the License.You may obtain a copy of the License at
	http://www.mozilla.org/MPL/MPL-1.1.html
	
	Software distributed under the License is distributed on an "AS IS"
	basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
	License for the specific language governing rights and limitations
	under the License.
	
	The Original Code is Fever Framework code.
	
	The Initial Developer of the Original Code is Romain Ecarnot.
	Portions created by Initial Developer are Copyright (C) 2006
	the Initial Developer. All Rights Reserved.
	
	Contributor(s):
	
	Unless required by applicable law or agreed to in writing, software
	distributed under the License is distributed on an "AS IS" BASIS,
	WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
	See the License for the specific language governing permissions and
	limitations under the License.
-----------------------------------------------------------------------------*/

import com.bourre.events.BooleanEvent;
import com.bourre.events.EventType;
import com.bourre.events.IEvent;
import com.bourre.log.LogLevel;
import com.bourre.structures.Point;
import com.bourre.structures.Rectangle;
import com.bourre.utils.ClassUtils;

import fever.app.context.abstract.FeverContext;
import fever.app.context.abstract.IContextContainer;
import fever.app.context.ContextManager;
import fever.app.menu.FvContextMenu;
import fever.app.menu.FvContextMenuEvent;
import fever.app.menu.FvContextMenuItem;
import fever.conf.Configuration;
import fever.conf.FeverConfiguration;
import fever.core.ApplicationState;
import fever.display.FeverStage;
import fever.events.ApplicationEvent;
import fever.events.EventPriority;
import fever.events.FeverController;
import fever.events.FeverEventList;
import fever.events.PriorityEventBroadcaster;
import fever.FeverApplication;
import fever.log.FeverDebug;
import fever.utils.GAnalytic;
import fever.utils.StringUtil;

import flash.external.ExternalInterface;

/**
 * Initialize properly the <strong>Fever Framework API's</strong>.
 * 
 * <p>Never use this class directly, take a look at {@link fever.Fever} class example to 
 * see how to initialize your application properly.
 * 
 * <p>{@link #name} property is used by {@link fever.utils.GAnalytic} to start logical 
 * track directory.<br />
 * Default is the main access class name.
 * 
 * @see fever.Fever
 * @see fever.FeverApplication
 * 
 * @author Romain Ecarnot
 */
class fever.core.CoreApplication
{	
	//-------------------------------------------------------------------------
	// Events definition
	//-------------------------------------------------------------------------
	
	/** Broadcasted when application focus change. */
	public static var onFocusChangeEVENT : EventType = ApplicationEvent.onFocusChangeEVENT;
	
	/** Broadcasted when application is resized. */
	public static var onResizeEVENT : EventType = ApplicationEvent.onResizeEVENT;
	
	/** Broadcasted when application state change. */
	public static var onStateChangeEVENT : EventType = ApplicationEvent.onStateChangeEVENT;
	
	
	//-------------------------------------------------------------------------
	// Private properties
	//-------------------------------------------------------------------------
	
	private static var _instance : CoreApplication;
	private static var _bInitialization:Function = com.bourre.core.HashCodeFactory;
	
	private var _online : Boolean;
	private var _url : String;	private var _app : FeverApplication;	private var _contextMenu : FvContextMenu;
	private var _bInit : Boolean;
	private var _bRunning : Boolean;
	private var _sourceItem : FvContextMenuItem;
	private var _context : FeverContext;	private var _container : IContextContainer;
	private var _config : Configuration;
	private var _bConfigEnabled : Boolean;
	
	private var _name : String;
	private var _title : String;
	private var _appendtitle : String;
	private var _historytitle : String;
	
	private var _oEB : PriorityEventBroadcaster;
	private var _listenApplicationShutDown : Boolean;
	private var _state : ApplicationState;
	
	
	//-------------------------------------------------------------------------
	// Public Properties
	//-------------------------------------------------------------------------
	
	/** Read only. Reference to your main application class. */
	public function get thread() : FeverApplication { return _app; }
		
	/** Application context menu. */
	public function get contextMenu() : FvContextMenu { return _contextMenu; }
	public function set contextMenu( menu : FvContextMenu) : Void { _setContextMenu( menu ); }
	
	/** Read only. Defines view source url */
	public function get source() : String { return _sourceItem.url; }
	
	/** Read only. Application url path. */
	public function get url() : String { return _url; }
	
	/** Read only. Returns loaded configuration object. */
	public function get config() : FeverConfiguration { return _config.getConfig(); }
	
	/** Read only. Application name. */
	public function get name() : String { return _name; }
	
	/** Read only. Returns application state. */
	public function get state() : ApplicationState { return _state; }
	
	/** 
	 * Indicates if <strong>Fever</strong> must warn the user of the closing of the application.<br />
	 * Default is {@code true}
	 * 
	 * <p>If sets to {@code false}, all call to {@link #quit()} method is equal to {@code quit( false );}
	 */
	public function get listenApplicationShutDown() : Boolean { return _listenApplicationShutDown; }
	public function set listenApplicationShutDown ( p : Boolean ) : Void { _listenApplicationShutDown = p;	}
	
	
	//-------------------------------------------------------------------------
	// Public API
	//-------------------------------------------------------------------------
	
	/**
	 * Don't use it in your development.
	 * 
	 * <p>{@link fever.Fever} class control it.
	 */
	public static function getInstance( app : FeverApplication, enableConfig : Boolean ) : CoreApplication
	{
		if( !_instance ) _instance = new CoreApplication( app, enableConfig );
		return _instance;	
	}
	
	/**
	 * Adds passed-in {@code listener} for receiving passed-in {@code type} event type.
	 * 
	 * @param type Name of the Event.
	 * @param listener Listener object.
	 */
	public function addEventListener( type : EventType, listener ) : Void
	{
		_oEB.addEventListener.apply( _oEB, arguments );	
	}
	
	/**
	 * Removes passed-in {@code listener} that subscribed to passed-in {@code type} event type.
	 * 
	 * @param type Name of the Event.
	 * @param listener Listener object.
	 */
	public function removeEventListener( type : EventType, listener ) : Void
	{
		_oEB.removeEventListener( type, listener );	
	}
	
	/**
	 * Broadcasts event to suscribed listeners.
	 * 
	 * @param event an <strong>IEvent</strong> instance
	 */
	public function broadcastEvent( event : IEvent ) : Void
	{
		_oEB.broadcastEvent( event );
	}
	
	/**
	 * Returns argument value defined by passed-in {@code id}.
	 * 
	 * <p>In browser context, arguments are passed to application using <strong>swfobject.js</strong> 
	 * script or throw the location url using the 'get' location format.<br /> 
	 * 
	 * {@code
	 *   var o = new SWFObject( "Demo_installer.swf", "fswf", "100%", "100%", "8", "#000000" );
	 *   o.addVariable("myArg", "blabla");
	 *   o.write("flashcontent"); 
	 * }
	 * <br />or using an url like : http://www.mydomain.com/mypage.html?myArg=blabla
	 */
	public function getArgument( id : String )
	{
		return _container.getArgument( id );
	}
	
	/**
	 * Indicates if passed-in {@code id} argument is defined in application.
	 * 
	 * <p>In browser context, arguments are passed to application using <strong>swfobject.js</strong> 
	 * script or throw the location url using the 'get' location format.<br /> 
	 * 
	 * {@code
	 *   var o = new SWFObject( "demo.swf", "fswf", "100%", "100%", "8", "#000000" );
	 *   o.addVariable("myArg", "blabla");
	 *   o.write("flashcontent"); 
	 * }
	 * <br />or using an url like : http://www.mydomain.com/mypage.html?myArg=blabla
	 * 
	 * @return {@code true} if argument is defined, otherwise {@code false}
	 */
	public function hasArgument( id : String ) : Boolean
	{
		return ( getArgument( id ) != undefined );	
	}
	
	/**
	 * Sets application title.
	 * 
	 * <p>Uses defined context to set the new application container title.
	 * 
	 * <p>Take a look at {@link fever.app.context.ContextManager} to see how 
	 * to define context.
	 * 
	 * @param mainTitle main part of the title ( pass {@code null} to keep current )
	 * @param subTitle title complement ( pass {@code null} to keep current )
	 * @param historyCaption used to show history place ( pass {@code null} to keep current )
	 */
	public function setTitle( mainTitle : String, subTitle : String, historyCaption : String ) : Void
	{
		_title = ( mainTitle != null ) ? mainTitle : _title;
		_appendtitle = ( subTitle != null ) ? subTitle : _appendtitle;
		_historytitle = ( historyCaption != null ) ? historyCaption : _historytitle;
		
		_container.setTitle( _title + formatSubTitle( _appendtitle ) + formatHistoryTitle( _historytitle ) );
	}
	
	/**
	 * Formats the sub tile section of application title string.
	 */
	public function formatSubTitle( src : String ) : String
	{
		if( src.length > 0 ) return ( ' // ' + src );
		else return '';
	}
	
	/**
	 * Formats the history tile section of application title string.
	 */
	public function formatHistoryTitle( src : String ) : String
	{
		if( src.length > 0 ) return ( " [ " + src + " ]" );
		else return '';
	}
	
	/**
	 * Returns application title.
	 */
	public function getTitle() : String
	{
		return _title;
	}
	
	/**
	 * Returns full application title. ( with appended and history text )
	 */
	public function getFullTitle() : String
	{
		return _title + formatSubTitle( _appendtitle ) + formatHistoryTitle( _historytitle );	
	}
	
	/**
	 * Returns hard defined container title
	 */
	public function getCurrentTitle() : String
	{
		return _container.getTitle( );
	}
	
	/**
	 * Sets application size.
	 * 
	 * <p>Uses defined context to set the new application container size.
	 * 
	 * <p>Take a look at {@link fever.app.context.ContextManager} to see how 
	 * to define context.
	 */
	public function setSize( width : Number, height : Number ) : Void
	{
		_container.setSize( width, height );
	}
	
	/**
	 * Returns application size.
	 */
	public function getSize() : Rectangle
	{
		return _container.getSize( );
	}
	
	/**
	 * Sets application position in desktop.
	 * 
	 * <p>Uses defined context to set the new application container size.
	 * 
	 * <p>Take a look at {@link fever.app.context.ContextManager} to see how 
	 * to define context.
	 */
	public function setLocation( x : Number, y : Number ) : Void
	{
		_container.setLocation( x, y );
	}
	
	/**
	 * Returns application location.
	 */
	public function getLocation() : Point
	{
		return _container.getLocation( );
	}
	
	/**
	 * Defines fullscreen mode of application.
	 */
	public function setFullScreenMode( enabled : Boolean ) : Void
	{
		_container.setFullScreenMode( enabled );	
	}
	
	/** 
	 * Returns the fullscreen state.
	 */
	public function isFullScreen() : Boolean
	{
		return _container.isFullScreen();
	}
	
	/** 
	 * Indicates if application running online or not.
	 */
	public function isOnline() : Boolean
	{
		return _online;	
	}
	
	/**
	 * Opens a external document located on {@code path} using context 
	 * engine.
	 * 
	 * <p>For example <strong>BrowserContext</strong> opens document in a 
	 * new browser window, whereas <strong>StudioContext</strong> use his 
	 * proper open mecanism.
	 */
	public function openDocument( path : String ) : Void
	{
		if( path ) _context.openDocument( path );	
	}
	
	/**
	 * A simple call to Flash getURL() method.
	 */
	public function openURL( url : String, window : String, method : String  ) : Void
	{
		getURL.apply( this, arguments );
	}
	
	/**
	 * Launch external application and returns result ( if any )
	 */
	public function runProcess( app : String, args : String, workingDir : String, hidden : Boolean, waitFor : Boolean )
	{
		return _context.runProcess( app, args, workingDir, hidden, waitFor );	
	}
	
	/**
	 * Display a protect screen of the top of application to avoid user 
	 * interactions.
	 */
	public function protect() : Void
	{
		FeverController.getInstance().broadcastEvent( new BooleanEvent( FeverEventList.onLockInteractionEVENT, true ) );
	}
	
	/**
	 * Hides a possible protect screen defined using {@link #protect()} method.
	 */
	public function unprotect() : Void
	{
		FeverController.getInstance().broadcastEvent( new BooleanEvent( FeverEventList.onLockInteractionEVENT, false ) );
	}
	
	/**
	 * Quit application
	 */
	public function quit( force : Boolean ) : Void
	{
		if( !_listenApplicationShutDown ) force = true; 
		_context.quit( force );
	}
	
	/**
	 * Internal call. Don't use it in development.
	 * 
	 * <p>Call by {@link fever.Fever} controller class.
	 */
	public function init() : Void
	{
		if( _bInit ) 
		{
			FeverDebug.ERROR( 'Application already running' );
			return;
		}
		
		// stored application initialisation state
		_bInit = true;
		
		// retreive current application title
		_title = getCurrentTitle();
		
		// default track application
		GAnalytic.track();
		
		// check configuration system
		if( _bConfigEnabled ) _config.load();
		else start();
	}
	
	/**
	 * Internal call. Don't use it in development.
	 */
	public function start() : Void
	{
		if( !_bRunning )
		{
			_bRunning = true;
			_app.run();
		}
	}
	
	
	//-------------------------------------------------------------------------
	// Private implementation
	//-------------------------------------------------------------------------
	
	/**
	 * Constructor.
	 * 
	 * @param config Custom configuration class
	 */
	private function CoreApplication( app : FeverApplication, enableConfig : Boolean )
	{
		_oEB = new PriorityEventBroadcaster( this );
		
		_title = '';
		_appendtitle = '';
		_historytitle = '';
		
		_bInit = false;
		_bRunning = false;
		_listenApplicationShutDown = true;
		
		_app = app;
		
		_name = ClassUtils.getClassName( _app );
		
		var p : String = _level0._url.split( '://' )[0];
		_online = ( p == 'http' || p == 'https' ) ? true : false;
		
		if( _online )
		{
			_url = StringUtil.trim( _level0._url, '/' );
			_url = _url.substring( 0, _url.lastIndexOf( '/' ) );
		}
		else _url = null;
		
		_state = ApplicationState.NORMAL;
		
		_context = ContextManager.getInstance().context;		_container = ContextManager.getInstance().container;
		
		_buildDebuggingTunnel();
		_insertContextMenu();
		
		addEventListener( onStateChangeEVENT, this, _getApplicationState, EventPriority.HIGHER );
		
		_config = Configuration.getInstance();
		
		_bConfigEnabled = ( enableConfig == true );
	}
	
	private function _getApplicationState( event : ApplicationEvent ) : Void
	{
		_state = event.getTarget();	
	}
	
	/**
	 * Inserts Fever built-in menu ( thanks to leave this reference alive )
	 */
	private function _insertContextMenu(  ) : Void 
	{
		var m : FvContextMenu = new FvContextMenu();
		m.hideBuiltInItems( );
		
		if( hasArgument( 'sourceURL' ) )
		{
			_sourceItem = new FvContextMenuItem( 'View source' );
			_sourceItem.url = getArgument( 'sourceURL' );
			_sourceItem.enabled = true;
			_sourceItem.visible = true;
			_sourceItem.addEventListener( FvContextMenuItem.MENU_ITEM_SELECT, this, _goto );
			
			m.push( _sourceItem );
		}
		
		contextMenu = m;
	}
	
	/**
	 * Builds external interface tunnel to log javascript message 
	 * throw Fever Logging API if {@link fever.log.FeverDebug#isOn} 
	 * is {@code true}.
	 */
	private function _buildDebuggingTunnel() : Void
	{
		if( FeverDebug.isOn )
		{
			var isAvailable : Boolean = ExternalInterface.available;
			if( isAvailable )
			{
				var methodName : String = ( !_root.traceHandler ) ? "trace" : _root.traceHandler;
				var wasSuccessful : Boolean = ExternalInterface.addCallback( methodName, this, _logJavascriptMessage );
			}
		} 	
	}
	
	private function _logJavascriptMessage( o, level : Number) : Void
	{
		var logLevel : LogLevel;
		switch( level )
		{
			case 0 : logLevel = LogLevel.DEBUG;
					break;
			case 1 : logLevel = LogLevel.INFO;
					break;
			case 2 : logLevel = LogLevel.WARN;
					break;
			case 3 : logLevel = LogLevel.ERROR;
					break;
			case 4 : logLevel = LogLevel.FATAL;
					break;
			default : logLevel = LogLevel.INFO;
		}
		
		FeverDebug.LOG( o, logLevel );
	}
	
	/**
	 * Just a link to Fever webpage.
	 * 
	 * @param event {@link fever.app.menu.FvContextMenuEvent} instance
	 */
	private function _goto( event : FvContextMenuEvent ) : Void
	{
		var link : String = event.getItem().url;
		GAnalytic.trackOutBound( link );
		getURL( link, '__fever' );
	}
	
	/**
	 * Defines a new {@code FvContextMenu} for main application menu.
	 * 
	 * @param menu {@code FvContextMenu} instance
	 */
	private function _setContextMenu( menu : FvContextMenu ) : Void
	{
		_contextMenu = menu;
		FeverStage.getInstance().level0.menu = _contextMenu.menu;
	}
}